package net.oschina.bilbodai.common.beanutil.bean.core;

import net.oschina.bilbodai.common.beanutil.bean.PropertyDumpType;
import net.oschina.bilbodai.common.beanutil.bean.creator.IPropertyCodeApplierCreator;
import net.oschina.bilbodai.common.beanutil.bean.creator.SimpleToSimpleCreator;
import net.oschina.bilbodai.common.beanutil.bean.type.NullType;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.SOURCE_FIELD_COMMON;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.SOURCE_KEY_COMMON;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.SOURCE_KEY_UNKNOWN;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.SOURCE_TYPE_ASSIGN_TARGET_TYPE;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.SOURCE_VALUE_COMMON;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.SOURCE_VALUE_UNKNOWN;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.TARGET_FIELD_COMMON;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.TARGET_KEY_COMMON;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.TARGET_KEY_UNKNOWN;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.TARGET_VALUE_COMMON;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.TARGET_VALUE_UNKNOWN;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.BitSetType.USING_ATTR_PATH_INFO;
import static net.oschina.bilbodai.common.beanutil.bean.core.DecisionTable.Decision.create;

/**
 * 31位
 * <p>
 * {@code 0 --> 30}
 * <p>
 * 0 - 2: 源字段类型 <br> 类型详情如下: {@code 0:未知类型, 1:一般类型, 2:List, 3:Set, 4:Map, 5:ARRAY} <br>
 * 3 - 5: 目标字段类型  <br>
 * 6 - 8: 源字段元素键类型 <br>
 * 9 - 11: 目标字段元素键类型 <br>
 * 12 - 14: 源字段元素值类型 <br>
 * 15 - 17: 目标字段元素值类型 <br>
 * 18 : 源字段类型是否可直接赋值给目标字段类型
 * 19 : 源字段元素键类型是否可以直接赋值给目标字段元素键类型
 * 20 : 源字段元素值类型是否可以直接赋值给目标字段元素值类型
 * 21 : 是否有AttrPathInfo
 *
 * @author daimao  Date: 16/6/2 Time: 上午10:12
 * @version $Id$
 */
public final class DecisionTable {

    private static final Map<Decision, IPropertyCodeApplierCreator> DECISION_DEAL_MAP = new HashMap<>();

    static {
        addDeal(create(new BitSettable[] {
                SOURCE_FIELD_COMMON,
                TARGET_FIELD_COMMON,
                SOURCE_KEY_UNKNOWN,
                TARGET_KEY_UNKNOWN,
                SOURCE_VALUE_UNKNOWN,
                TARGET_VALUE_UNKNOWN,
                SOURCE_TYPE_ASSIGN_TARGET_TYPE,
        }), SimpleToSimpleCreator.INSTANCE);
        addDeal(create(new BitSettable[] {
                SOURCE_FIELD_COMMON,
                TARGET_FIELD_COMMON,
                SOURCE_KEY_UNKNOWN,
                TARGET_KEY_UNKNOWN,
                SOURCE_VALUE_UNKNOWN,
                TARGET_VALUE_UNKNOWN,
                USING_ATTR_PATH_INFO,
        }), SimpleToSimpleCreator.INSTANCE);
    }

    public static IPropertyCodeApplierCreator findDeal(Decision decision) {
        return DECISION_DEAL_MAP.get(decision);
    }

    private static void addDeal(Decision decision, IPropertyCodeApplierCreator deal) {
        DECISION_DEAL_MAP.put(decision, deal);
    }

    public enum BitSetType implements BitSettable {
        NOOP(0x0),
        SOURCE_FIELD_UNKNOWN(0x0),
        SOURCE_FIELD_COMMON(0x1),
        SOURCE_FIELD_LIST(0x2),
        SOURCE_FIELD_SET(0x3),
        SOURCE_FIELD_MAP(0x4),
        SOURCE_FIELD_ARRAY(0x5),

        TARGET_FIELD_UNKNOWN(0x0),
        TARGET_FIELD_COMMON(0x1 << 3),
        TARGET_FIELD_LIST(0x2 << 3),
        TARGET_FIELD_SET(0x3 << 3),
        TARGET_FIELD_MAP(0x4 << 3),
        TARGET_FIELD_ARRAY(0x5 << 3),

        SOURCE_KEY_UNKNOWN(0x0),
        SOURCE_KEY_COMMON(0x1 << 6),
        SOURCE_KEY_LIST(0x2 << 6),
        SOURCE_KEY_SET(0x3 << 6),
        SOURCE_KEY_MAP(0x4 << 6),
        SOURCE_KEY_ARRAY(0x5 << 6),

        TARGET_KEY_UNKNOWN(0x0),
        TARGET_KEY_COMMON(0x1 << 9),
        TARGET_KEY_LIST(0x2 << 9),
        TARGET_KEY_SET(0x3 << 9),
        TARGET_KEY_MAP(0x4 << 9),
        TARGET_KEY_ARRAY(0x5 << 9),

        SOURCE_VALUE_UNKNOWN(0x0),
        SOURCE_VALUE_COMMON(0x1 << 12),
        SOURCE_VALUE_LIST(0x2 << 12),
        SOURCE_VALUE_SET(0x3 << 12),
        SOURCE_VALUE_MAP(0x4 << 12),
        SOURCE_VALUE_ARRAY(0x5 << 12),

        TARGET_VALUE_UNKNOWN(0x0),
        TARGET_VALUE_COMMON(0x1 << 15),
        TARGET_VALUE_LIST(0x2 << 15),
        TARGET_VALUE_SET(0x3 << 15),
        TARGET_VALUE_MAP(0x4 << 15),
        TARGET_VALUE_ARRAY(0x5 << 15),

        SOURCE_TYPE_ASSIGN_TARGET_TYPE(0x20000),
        SOURCE_KEY_ASSIGN_TARGET_KEY(0x40000),
        SOURCE_VALUE_ASSIGN_TARGET_VALUE(0x80000),
        USING_ATTR_PATH_INFO(0x100000);

        private final int code;

        BitSetType(int code) {
            this.code = code;
        }

        @Override
        public int set(int code) {
            return this.code | code;
        }
    }

    public interface BitSettable {
        int set(int code);
    }

    public final static class Decision {
        private int code = 0x0;

        static Decision create(BitSettable[] settables) {
            Decision decision = new Decision();
            for (BitSettable settable : settables) {
                decision.code = settable.set(decision.code);
            }
            return decision;
        }

        public Decision decideFieldType(Class<?> type, FieldTypeShift shift) {
            int tmpCode = 0x0;
            if (List.class.isAssignableFrom(type)) {
                tmpCode = FieldType.LIST.set(tmpCode);
            } else if (Set.class.isAssignableFrom(type)) {
                tmpCode = FieldType.SET.set(tmpCode);
            } else if (Map.class.isAssignableFrom(type)) {
                tmpCode = FieldType.MAP.set(tmpCode);
            } else if (type.isArray()) {
                tmpCode = FieldType.ARRAY.set(tmpCode);
            } else if (NullType.class.equals(type)) {
                tmpCode = FieldType.UNKNOWN.set(tmpCode);
            } else {
                tmpCode = FieldType.COMMON.set(tmpCode);
            }
            tmpCode = tmpCode << shift.getShift();
            this.code |= tmpCode;
            return this;
        }

        public Decision decideBitSet(BitSetType type) {
            this.code |= type.set(this.code);
            return this;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            Decision decision = (Decision) o;
            return code == decision.code;
        }

        @Override
        public int hashCode() {
            return Objects.hash(code);
        }
    }

    public static void main(String[] args) {
        Decision decision = new Decision();
        decision.decideFieldType(DecisionTable.class, FieldTypeShift.TARGET_FIELD);
        decision.decideFieldType(FieldType.class, FieldTypeShift.SOURCE_FIELD);
        System.out.println(decision.code);
    }

    public enum FieldType {
        UNKNOWN(0x0),
        COMMON(0x1),
        LIST(0x2),
        SET(0x3),
        MAP(0x4),
        ARRAY(0x5);
        private final int code;

        FieldType(int code) {
            this.code = code;
        }

        public int getCode() {
            return code;
        }

        public int set(int code) {
            return this.getCode() | code;
        }
    }

    public enum FieldTypeShift {
        SOURCE_FIELD(0),
        TARGET_FIELD(3),
        SOURCE_KEY(6),
        TARGET_KEY(9),
        SOURCE_VALUE(12),
        TARGET_VALUE(15);
        private final int shift;

        FieldTypeShift(int shift) {
            this.shift = shift;
        }

        public int getShift() {
            return shift;
        }
    }

    public enum Deal {

        ;
        private final PropertyDumpType srcType;
        private final PropertyDumpType targetType;

        Deal(PropertyDumpType srcType, PropertyDumpType targetType) {
            this.srcType = srcType;
            this.targetType = targetType;
        }

        public PropertyDumpType getSrcType() {
            return srcType;
        }

        public PropertyDumpType getTargetType() {
            return targetType;
        }
    }
}